package net.scribblemedia.candle.mlengine;

import static net.scribblemedia.candle.data.pattern.PatternChangeType.IRRELEVANT_CHANGE;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collection;

import net.scribblemedia.candle.data.download.MarketDataDownloader;
import net.scribblemedia.candle.data.pattern.PatternWindow;
import net.scribblemedia.candle.data.pattern.PatternWindowFactory;
import net.scribblemedia.candle.data.pattern.PatternWindowService;
import net.scribblemedia.candle.data.trainingset.TrainingSetHolder;

import org.apache.commons.io.FileUtils;
import org.apache.log4j.Logger;
import org.encog.ml.data.MLData;
import org.encog.ml.data.MLDataPair;
import org.encog.ml.data.MLDataSet;
import org.encog.ml.data.market.MarketDataType;
import org.encog.ml.data.market.loader.LoadedMarketData;
import org.encog.neural.networks.BasicNetwork;
import org.encog.persist.EncogDirectoryPersistence;
import org.joda.time.DateMidnight;

public class TestHarness {
	private static Logger LOG = Logger.getLogger(TestHarness.class);
	
	private static final double TARGET_ERROR_RATE = 0.1d;
	private static final int NOSE_SIZE_DAYS = 30;
	private static final int TAIL_SIZE_DAYS = 60;
	private static final double MINIMUM_GAIN_PERCENTAGE = 20.0d;
	private static final double FLAT_GAIN_THRESHOLD_PERCENTAGE = 2d;
	private static final double NOSE_SPREAD_DAYS = 5d;
	
	private static String[] TRAINING_STOCKS = { "LLOY.L", "BARC.L", "EMG.L",
			"ARM.L", "CKSN.L", "GLEN.L", "ITRK.L", "AGK.L", "WEIR.L", "ADM.L",
			"AZN.L", "CNA.L", "EVR.L", "ITV.L", "KAZ.L", "MRW.L", "PNN.L",
			"RIO.L", "SHP.L", "UU.L", "WTB.L" };
	
	public static void main(String[] args) throws IOException {
		
		Collection<LoadedMarketData> marketData = new ArrayList<LoadedMarketData>();
		
		for (String symbol : TRAINING_STOCKS) {
			addMarketDataToTrainingSet(marketData, symbol);
		}
		
		Collection<PatternWindow> patternWindows = new PatternWindowFactory().createPatternWindows(new ArrayList<LoadedMarketData>(marketData), TAIL_SIZE_DAYS, NOSE_SIZE_DAYS);
		
		patternWindows = filterPatternWindows(patternWindows);
		
		File patternWindowsFile = new File(String.valueOf(System.currentTimeMillis()) + "-patternWindows.txt");
		LOG.info("Going to train on " + patternWindows.size() + " pattern windows");
		LOG.info("Writing pattern windows file: " + patternWindowsFile.getAbsolutePath());
		
		FileUtils.writeStringToFile(patternWindowsFile, buildPatternWindowsString(patternWindows));
		
		TrainingSetHolder trainingSetHolder = new PatternWindowService().buildTrainingSet(patternWindows, MINIMUM_GAIN_PERCENTAGE, NOSE_SPREAD_DAYS, FLAT_GAIN_THRESHOLD_PERCENTAGE);
		FlexibleNetwork flexibleNetwork = new FlexibleNetwork(trainingSetHolder);
		BasicNetwork network = flexibleNetwork.train(TARGET_ERROR_RATE);
		
		MLDataSet trainingSet = flexibleNetwork.getTrainingSet();
		
		ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); 
		EncogDirectoryPersistence.saveObject(outputStream, network);
		byte[] byteArray = outputStream.toByteArray();
		
		EncogDirectoryPersistence.saveObject(new File(String.valueOf(System.currentTimeMillis()) + ".encog"), network);
		
		// test the neural network
		LOG.info("Neural Network Results:");
		
		for (MLDataPair pair : trainingSet) {
			final MLData output = network.compute(pair.getInput());
			double[] inputArray = pair.getInputArray();
			StringBuffer buf = new StringBuffer();
			int index = 0;
			for (double value : inputArray) {
				buf.append(value);
				if (index < inputArray.length) {
					buf.append(",");
				}
				index++;
			}
			
			LOG.info("Input: " + buf.toString() + " :: Output: "
					+ output.getData(0) + " :: Ideal: "
					+ pair.getIdeal().getData(0));
		}
	}

	private static void addMarketDataToTrainingSet(Collection<LoadedMarketData> marketData,
			String symbol) {
		Collection<LoadedMarketData> marketData1 = new MarketDataDownloader(new DateMidnight(2010,1,1).toDate(), new DateMidnight(2012,8,20).toDate(), symbol).retrieveMarketData();
		marketData.addAll(marketData1);
	}

	private static Collection<PatternWindow> filterPatternWindows(Collection<PatternWindow> patternWindows) {
		Collection<PatternWindow> filteredPatternWindows = new ArrayList<PatternWindow>();
		for (PatternWindow patternWindow : patternWindows) {
			if (IRRELEVANT_CHANGE != new PatternWindowService().isIndicitivePatternWindow(patternWindow, MINIMUM_GAIN_PERCENTAGE, NOSE_SPREAD_DAYS, FLAT_GAIN_THRESHOLD_PERCENTAGE)) {
				filteredPatternWindows.add(patternWindow);
			}
		}
		return filteredPatternWindows;
	}

	private static String buildPatternWindowsString(Collection<PatternWindow> patternWindows) {
		StringBuffer buf = new StringBuffer();
		for (PatternWindow patternWindow : patternWindows) {
			buf.append("\"Tail Data\"\n");
			createLoadedMarketDataCsv(buf, patternWindow.getTailData());
			buf.append("\"Nose Data\"\n");
			createLoadedMarketDataCsv(buf, patternWindow.getNoseData());
		}

		return buf.toString();
	}

	private static void createLoadedMarketDataCsv(StringBuffer buf,
			Collection<LoadedMarketData> data) {
		for (LoadedMarketData dataItem : data) {
			buf.append("\"");
			buf.append(dataItem.getTicker().getSymbol());
			buf.append("\",\"");
			buf.append(new SimpleDateFormat("yyyy/MM/dd").format(dataItem.getWhen()));
			buf.append("\",\"");			
			buf.append(dataItem.getData(MarketDataType.OPEN));
			buf.append("\",\"");	
			buf.append(dataItem.getData(MarketDataType.CLOSE));
			buf.append("\",\"");	
			buf.append(dataItem.getData(MarketDataType.HIGH));
			buf.append("\",\"");	
			buf.append(dataItem.getData(MarketDataType.LOW));
			buf.append("\",\"");	
			buf.append(dataItem.getData(MarketDataType.VOLUME));
			buf.append("\",\"");	
			buf.append(dataItem.getData(MarketDataType.ADJUSTED_CLOSE));
			buf.append("\"");
			buf.append("\n");
		}
	}
}
